package logic

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/spf13/cast"
	"github.com/spf13/viper"
	"nhooyr.io/websocket"
	"nhooyr.io/websocket/wsjson"
	"regexp"
	"strings"
	"sync/atomic"
	"time"
)

var globalUID uint32 = 0

// System 系统用户，代表系统主动发送的消息
var System = &User{} //UID 是 0

// User 代表进入聊天室的用户
type User struct {
	UID            int           `json:"uid,omitempty"`
	NickName       string        `json:"nickname,omitempty"`
	EnterAt        time.Time     `json:"enter_at"`
	Addr           string        `json:"addr,omitempty"`
	MessageChannel chan *Message `json:"-"`

	IsNew bool //判断用户是不是第一次加入聊天室
	Token string

	conn *websocket.Conn
}

func NewUser(conn *websocket.Conn, token, nickname, addr string) *User {
	user := &User{
		NickName:       nickname,
		EnterAt:        time.Now(),
		Addr:           addr,
		MessageChannel: make(chan *Message, 8),
		Token:          token,
		conn:           conn,
	}

	if user.Token != "" {
		uid, err := parseTokenAndValidate(token, nickname)
		if err == nil {
			user.UID = uid
		}
	}

	if user.UID == 0 {
		user.UID = int(atomic.AddUint32(&globalUID, 1))
		user.Token = genToken(user.UID, user.NickName)
		user.IsNew = true
	}
	return user
}

func genToken(uid int, nickname string) string {
	secret := viper.GetString("token-secret")
	message := fmt.Sprintf("%s%s%d", nickname, secret, uid) //待 Hash 的字符串

	messageMAC := macSha256([]byte(message), []byte(secret)) //使用 HMAC-SHA256 算法计算出 Hash 值

	return fmt.Sprintf("%suid%d", base64.StdEncoding.EncodeToString(messageMAC), uid) //Base64 编码只是将字节数组转换为字符串，并非将原始字符串进行编码
}

func macSha256(message, secret []byte) []byte {
	mac := hmac.New(sha256.New, secret)
	mac.Write(message)
	return mac.Sum(nil)
}

// Token 的解析和校验
func parseTokenAndValidate(token, nickname string) (int, error) {
	pos := strings.LastIndex(token, "uid")
	messageMAC, err := base64.StdEncoding.DecodeString(token[:pos])
	if err != nil {
		return 0, err
	}
	uid := cast.ToInt(token[pos+3:])

	secret := viper.GetString("token-secret")
	message := fmt.Sprintf("%s%s%d", nickname, secret, uid)

	ok := validateMAC([]byte(message), messageMAC, []byte(secret))
	if ok {
		return uid, nil
	}

	return 0, errors.New("token is illegal")
}

// validateMAC 验证消息的 MAC（消息认证码）是否与预期的 MAC 相匹配。
// 使用 HMAC-SHA256 算法对消息进行计算，并与预期的 MAC 进行比较。
// 返回 true 表示 MAC 匹配，返回 false 表示 MAC 不匹配。
func validateMAC(message, messageMAC, secret []byte) bool {
	mac := hmac.New(sha256.New, secret)        // 创建一个新的 HMAC 实例，使用 SHA256 散列函数和密钥
	mac.Write(message)                         // 将消息写入 HMAC 实例，计算 MAC
	expectedMAC := mac.Sum(nil)                // 计算预期的 MAC
	return hmac.Equal(messageMAC, expectedMAC) // 比较消息的 MAC 和预期的 MAC，并返回比较结果
	//因为 HMAC-SHA256 得到的结果是二进制的，因此相等比较使用了 hmac 包的 Equal 函数。
}

func (u *User) SendMessage(ctx context.Context) {
	for msg := range u.MessageChannel { //从当前用户的 MessageChannel 中读取消息
		wsjson.Write(ctx, u.conn, msg) //将消息发送给浏览器
	}
}

// CloseMessageChannel 避免 goroutine 泄露
func (u *User) CloseMessageChannel() {
	close(u.MessageChannel)
}

func (u *User) ReceiveMessage(ctx context.Context) error {
	var (
		receiveMsg map[string]string
		err        error
	)
	for {
		err = wsjson.Read(ctx, u.conn, &receiveMsg)
		if err != nil {
			//判断连接是否已经关闭，若正常关闭，则不认为是错误
			var closeErr websocket.CloseError
			if errors.As(err, &closeErr) {
				return nil
			}

			return err
		}

		//把内容发送到聊天室
		sendMsg := NewMessage(u, receiveMsg["content"], receiveMsg["send_time"])
		//过滤敏感词
		sendMsg.Content = FilterSensitive(sendMsg.Content)

		////解析 content，看是否是一条私信消息
		//sendMsg.Content = strings.TrimSpace(sendMsg.Content)
		////注意：这个实现要求必须是 @ 开始，消息中间的 @ 没有进行处理。
		//if strings.HasPrefix(sendMsg.Content, "@") {
		//	sendMsg.To = strings.SplitN(sendMsg.Content, " ", 2)[0][1:] //使用 " "(空格) 作为分隔符，最多分割为 2 个子串
		//}
		//解析 content，看见都 @ 谁了
		reg := regexp.MustCompile(`@[^\s@]{2,20}`) //昵称必须在 2 至 20 个字符之间，并且与前面的昵称校验保持一致
		sendMsg.Ats = reg.FindAllString(sendMsg.Content, -1)

		Broadcaster.Broadcast(sendMsg)
	}
}
